from flask import Flask, request, render_template, jsonify, send_file
import textfsm
import io
import os
import re
import logging
import traceback
import sqlite3
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Font, Border, Side, Alignment
from io import BytesIO
from datetime import datetime

app = Flask(__name__)
DB_PATH = "textfsm.db"
LOG_DIR = "logs"
PASSWORD = "123"  # 管理模板密码
MAX_TEXT_SIZE = 10 * 1024 * 1024  # 10MB
MAX_TEMPLATE_SIZE = 1 * 1024 * 1024  # 1MB

# 初始化目录
if not os.path.exists(LOG_DIR):
    os.makedirs(LOG_DIR)

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    filename=os.path.join(LOG_DIR, 'app.log'),
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)

# 全局变量存储日志级别
current_log_level = logging.INFO

def set_log_level(debug_mode):
    """设置日志级别"""
    global current_log_level
    try:
        if debug_mode:
            current_log_level = logging.DEBUG
            logger.setLevel(logging.DEBUG)
            # 添加控制台处理器，方便实时查看日志
            console_handler = logging.StreamHandler()
            console_handler.setLevel(logging.DEBUG)
            formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
            console_handler.setFormatter(formatter)
            logger.addHandler(console_handler)
            logger.debug("日志级别已切换到DEBUG，开始记录详细日志")
        else:
            current_log_level = logging.INFO
            logger.setLevel(logging.INFO)
            # 移除控制台处理器
            for handler in logger.handlers[:]:
                if isinstance(handler, logging.StreamHandler):
                    logger.removeHandler(handler)
            logger.info("日志级别已切换到INFO，仅记录重要信息")
        return True
    except Exception as e:
        logger.error(f"设置日志级别失败: {str(e)}")
        return False

def init_db():
    """初始化数据库"""
    try:
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS templates (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT UNIQUE NOT NULL,
                content TEXT NOT NULL,
                category TEXT DEFAULT '未分类',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        conn.commit()
        logger.info("数据库初始化成功")
    except Exception as e:
        logger.error(f"数据库初始化失败: {str(e)}")
        raise
    finally:
        conn.close()

def get_db():
    """获取数据库连接"""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn

def sanitize_filename(name):
    """清理文件名，确保安全性和长度限制"""
    try:
        name = re.sub(r'[\/\\:*?"<>|]', '', name).strip().rstrip('.')
        if len(name) > 100 or '..' in name or name.startswith('/'):
            raise ValueError("非法文件名或名称过长")
        return name
    except Exception as e:
        logger.error(f"文件名清理失败: {str(e)}")
        raise

def check_password(provided_password):
    """验证密码"""
    return provided_password == PASSWORD

def get_templates():
    """获取模板列表及其分类"""
    try:
        conn = get_db()
        cursor = conn.cursor()
        cursor.execute('SELECT name, category FROM templates ORDER BY category, name')
        templates = [dict(row) for row in cursor.fetchall()]
        logger.info(f"从数据库加载模板列表: {[t['name'] for t in templates]}")
        return templates
    except Exception as e:
        logger.error(f"获取模板列表失败: {str(e)}")
        return []
    finally:
        conn.close()

def preprocess_text(text, ignore_timestamp=False, timestamp_regex=r'^\[\d{8}_\d{6}\]'):
    """预处理文本，移除时间戳"""
    if ignore_timestamp and timestamp_regex:
        try:
            text = re.sub(timestamp_regex, '', text, flags=re.MULTILINE)
            logger.info(f"移除时间戳，使用正则: {timestamp_regex}")
        except re.error as e:
            logger.warning(f"无效正则 {timestamp_regex}: {str(e)}")
    return text

@app.route('/', methods=['GET'])
def index():
    """首页路由"""
    try:
        templates = get_templates()
        logger.info(f"加载模板列表: {[t['name'] for t in templates]}")
        return render_template('index.html', templates=templates)
    except Exception as e:
        logger.error(f"加载首页失败: {str(e)}\n{traceback.format_exc()}")
        return render_template('index.html', templates=[]), 500

@app.route('/api/parse', methods=['POST'])
def api_parse():
    """解析文本API"""
    try:
        data = request.get_json() or {}
        text = data.get('text', '')
        template_content = data.get('template_content', '')
        template_name = data.get('template_name', '')
        ignore_timestamp = data.get('ignore_timestamp', False)
        timestamp_regex = data.get('timestamp_regex', r'^\[\d{8}_\d{6}\]')

        logger.debug(f"收到解析请求: template_name={template_name}, ignore_timestamp={ignore_timestamp}")
        logger.debug(f"时间戳正则表达式: {timestamp_regex}")

        if not text:
            logger.warning("缺少参数: text")
            return jsonify({'error': '缺少参数: text'}), 400
        if len(text) > MAX_TEXT_SIZE:
            logger.warning(f"输入文本过大: {len(text)} bytes")
            return jsonify({'error': f'输入文本过大，最大支持{MAX_TEXT_SIZE//1024//1024}MB'}), 400
        if template_content and len(template_content) > MAX_TEMPLATE_SIZE:
            logger.warning(f"模板内容过大: {len(template_content)} bytes")
            return jsonify({'error': f'模板内容过大，最大支持{MAX_TEMPLATE_SIZE//1024}KB'}), 400

        processed_text = preprocess_text(text, ignore_timestamp, timestamp_regex)
        logger.debug(f"预处理后的文本长度: {len(processed_text)}")

        if template_content:
            parser = textfsm.TextFSM(io.StringIO(template_content))
            logger.debug("使用直接输入的模板解析")
        elif template_name:
            template_name = sanitize_filename(template_name)
            conn = get_db()
            cursor = conn.cursor()
            cursor.execute('SELECT content FROM templates WHERE name = ?', (template_name,))
            result = cursor.fetchone()
            if not result:
                logger.warning(f"模板不存在: {template_name}")
                return jsonify({'error': f'模板 {template_name} 不存在'}), 404
            parser = textfsm.TextFSM(io.StringIO(result['content']))
            logger.debug(f"使用数据库中的模板: {template_name}")
            conn.close()
        else:
            logger.warning("缺少模板内容或名称")
            return jsonify({'error': '缺少模板内容或名称'}), 400

        parsed_result = parser.ParseText(processed_text)
        headers = parser.header
        result = [dict(zip(headers, row)) for row in parsed_result]
        logger.debug(f"解析结果: headers={headers}, rows={result}")
        logger.info(f"解析成功，原始行数: {len(parsed_result)}，返回行数: {len(result)}")
        return jsonify({'status': 'success', 'headers': headers, 'rows': result})
    except Exception as e:
        logger.error(f"解析失败: {str(e)}\n{traceback.format_exc()}")
        return jsonify({'error': f'解析失败: {str(e)}'}), 400

@app.route('/api/save_template', methods=['POST'])
def save_template():
    """保存模板API"""
    try:
        data = request.get_json() or {}
        if not check_password(data.get('password', '')):
            logger.warning("密码错误")
            return jsonify({'error': '密码错误，无权限保存模板'}), 403

        template_name = sanitize_filename(data.get('template_name', ''))
        template_content = data.get('template_content', '')
        category = data.get('category', '未分类')

        if not template_name or not template_content:
            logger.warning("缺少模板名称或内容")
            return jsonify({'error': '模板名称或内容为空'}), 400

        conn = get_db()
        cursor = conn.cursor()
        try:
            cursor.execute('''
                INSERT OR REPLACE INTO templates (name, content, category, updated_at)
                VALUES (?, ?, ?, CURRENT_TIMESTAMP)
            ''', (template_name, template_content, category))
            conn.commit()
            logger.info(f"模板保存成功: {template_name}")
            return jsonify({'status': 'success', 'templates': get_templates(), 'saved_name': template_name})
        except sqlite3.IntegrityError:
            logger.warning(f"模板名称已存在: {template_name}")
            return jsonify({'error': '模板名称已存在'}), 400
        finally:
            conn.close()
    except Exception as e:
        logger.error(f"保存模板失败: {str(e)}\n{traceback.format_exc()}")
        return jsonify({'error': f'保存模板失败: {str(e)}'}), 400

@app.route('/api/get_template', methods=['POST'])
def get_template():
    """获取模板API"""
    conn = None
    try:
        data = request.get_json() or {}
        template_name = sanitize_filename(data.get('template_name', ''))
        if not template_name:
            logger.warning("缺少模板名称")
            return jsonify({'error': '缺少模板名称'}), 400

        conn = get_db()
        cursor = conn.cursor()
        cursor.execute('SELECT content, category FROM templates WHERE name = ?', (template_name,))
        result = cursor.fetchone()
        if not result:
            logger.warning(f"模板不存在: {template_name}")
            return jsonify({'error': f'模板 {template_name} 不存在'}), 404

        logger.info(f"获取模板成功: {template_name}")
        return jsonify({
            'status': 'success',
            'content': result['content'],
            'category': result['category']
        })
    except Exception as e:
        logger.error(f"获取模板失败: {str(e)}\n{traceback.format_exc()}")
        return jsonify({'error': f'获取模板失败: {str(e)}'}), 400
    finally:
        if conn:
            conn.close()

@app.route('/api/delete_template', methods=['POST'])
def delete_template():
    """删除模板API"""
    try:
        data = request.get_json() or {}
        if not check_password(data.get('password', '')):
            logger.warning("密码错误")
            return jsonify({'error': '密码错误，无权限删除模板'}), 403

        template_name = sanitize_filename(data.get('template_name', ''))
        if not template_name:
            logger.warning("缺少模板名称")
            return jsonify({'error': '缺少模板名称'}), 400

        conn = get_db()
        cursor = conn.cursor()
        cursor.execute('DELETE FROM templates WHERE name = ?', (template_name,))
        if cursor.rowcount == 0:
            logger.warning(f"模板不存在: {template_name}")
            return jsonify({'error': f'模板 {template_name} 不存在'}), 404

        conn.commit()
        logger.info(f"模板删除成功: {template_name}")
        return jsonify({'status': 'success', 'templates': get_templates()})
    except Exception as e:
        logger.error(f"删除模板失败: {str(e)}\n{traceback.format_exc()}")
        return jsonify({'error': f'删除模板失败: {str(e)}'}), 400
    finally:
        conn.close()

@app.route('/api/export_excel', methods=['POST'])
def export_excel():
    """导出Excel API，List值自动换行"""
    try:
        data = request.get_json() or {}
        headers = data.get('headers', [])
        rows = data.get('rows', [])
        if not headers or not rows:
            logger.warning("无数据可导出")
            return jsonify({'error': '无数据可导出'}), 400

        logger.info(f"导出Excel，接收到的行数: {len(rows)}")
        logger.debug(f"表头: {headers}")

        wb = Workbook()
        ws = wb.active
        ws.title = "解析结果"
        ws.append(['序号'] + headers)

        # 样式定义
        header_fill = PatternFill(start_color='D3D3D3', end_color='D3D3D3', fill_type='solid')
        header_font = Font(bold=True)
        border = Border(left=Side(style='thin'), right=Side(style='thin'),
                        top=Side(style='thin'), bottom=Side(style='thin'))
        center_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)

        # 设置表头样式
        for cell in ws[1]:
            cell.fill = header_fill
            cell.font = header_font
            cell.border = border
            cell.alignment = center_alignment

        # 填充数据并应用样式
        for i, row in enumerate(rows, 1):
            row_data = [i]
            logger.debug(f"处理第{i}行数据: {row}")
            
            for h in headers:
                value = row.get(h, '')
                logger.debug(f"处理字段 {h} 的值: {repr(value)}")
                
                if isinstance(value, list):
                    # 列表转多行文本
                    value = '\n'.join(str(item).strip() for item in value)
                    logger.debug(f"列表转换后的值: {repr(value)}")
                else:
                    # 处理字符串，将连续的空格转换为换行符
                    value = str(value).strip()
                    # 将连续的空格替换为换行符
                    value = re.sub(r'\s+', '\n', value)
                    # 确保换行符统一
                    value = value.replace('\r\n', '\n').replace('\r', '\n')
                    logger.debug(f"字符串处理后的值: {repr(value)}")
                
                row_data.append(value)
            
            ws.append(row_data)
            logger.debug(f"第{i}行数据已添加到Excel: {row_data}")

            # 设置每个单元格样式并调试输出
            for col_index, cell in enumerate(ws[i + 1], 1):
                cell.border = border
                cell.alignment = center_alignment
                cell.number_format = '@'  # 设置文本格式
                logger.debug(f"第{i}行第{col_index}列内容: {repr(cell.value)}")
                logger.debug(f"第{i}行第{col_index}列样式: wrap_text={cell.alignment.wrap_text}, "
                          f"horizontal={cell.alignment.horizontal}, vertical={cell.alignment.vertical}")

        # 设置列宽
        ws.column_dimensions['A'].width = 5
        for col_num in range(2, len(headers) + 2):
            col_letter = chr(64 + col_num) if col_num <= 90 else 'A' + chr(64 + col_num - 26)
            ws.column_dimensions[col_letter].width = 20
            logger.debug(f"设置列 {col_letter} 宽度为 20")

        # 设置行高
        for i in range(1, ws.max_row + 1):
            ws.row_dimensions[i].height = 30
            logger.debug(f"设置第{i}行高度为 30")

        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        filename = f'result_{timestamp}.xlsx'
        excel_file = BytesIO()
        wb.save(excel_file)
        excel_file.seek(0)

        logger.info(f"Excel导出成功: {filename}，写入行数: {ws.max_row - 1}")
        return send_file(
            excel_file,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=filename
        )
    except Exception as e:
        logger.error(f"导出Excel失败: {str(e)}\n{traceback.format_exc()}")
        return jsonify({'error': f'导出Excel失败: {str(e)}'}), 500

@app.route('/api/set_log_level', methods=['POST'])
def api_set_log_level():
    """设置日志级别API"""
    try:
        data = request.get_json() or {}
        debug_mode = data.get('debug_mode', False)
        
        if set_log_level(debug_mode):
            logger.debug(f"收到日志级别切换请求: debug_mode={debug_mode}")
            return jsonify({
                'status': 'success',
                'debug_mode': debug_mode,
                'message': '日志级别设置成功'
            })
        else:
            logger.error("设置日志级别失败")
            return jsonify({'error': '设置日志级别失败'}), 500
    except Exception as e:
        logger.error(f"设置日志级别失败: {str(e)}\n{traceback.format_exc()}")
        return jsonify({'error': f'设置日志级别失败: {str(e)}'}), 500

if __name__ == '__main__':
    init_db()  # 初始化数据库
    app.run(host='0.0.0.0', port=5012, debug=True) 